package org.heyma.core.service.impl;

import com.google.gson.Gson;
import com.icg.entityclassutils.*;
import org.heyma.core.dao.Dao;
import org.heyma.core.service.IEntityServices;
import java.io.IOException;
import java.lang.Class;
import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import net.sf.json.JSONArray;
import org.apache.commons.beanutils.PropertyUtils;
import org.heyma.db.utils.StatementUtil;
import org.heyma.db.utils.Table;
import org.heyma.packageutils.HeymaPakkageUtils;
import org.heyma.packageutils.extja.treeloader.Element;
import org.heyma.packageutils.extja.treeloader.Folder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * Contact Service
 *
 * @since 2011-02-13
 * @author Johnston Castillo Valencia email: john.gnu@gmail.com
 */
@Service
public class EntityServices implements IEntityServices {

    @Autowired
    Dao dao;
    private Map<String, Object> database = new HashMap<String, Object>();
    /* System Cache Store */
    private Map<String, Object> entityStruct = new HashMap<String, Object>();
    private Map<String, Object> entityDataCache = new HashMap<String, Object>();

    @Override
    public void clearCache() {
        entityStruct.clear();
        entityDataCache.clear();
    }

    @Override
    public void persistRam(Map<String, Object> insertable, String id) {
        String pk = id + "_id";
        if (database.get(id) == null) {
            database.put(id, new HashMap());
        }
        if (insertable.get(pk) == null) {
            insertable.put(pk, System.currentTimeMillis());
        }
        Map records = (Map) database.get(id);
        records.put(pk, insertable);
    }

    @Override
    public Map<String, Object> dataRam(String id) {
        return (Map<String, Object>) database.get(id);
    }

    @Override
    public Map<String, Object> getRam(String entity, Long id) {
        Map records = (Map) database.get(entity);
        return (Map<String, Object>) records.get(id);
    }

    @Override
    public void deleteObjectRam(String entity, Long id) {
        Map records = (Map) database.get(entity);
        records.remove(id);
    }

    /**
     * Generates
     *
     * @return Un Array [] JSON de los paquetes y clases para el "treeloader"
     * del entity manager
     */
    public List<Folder> getEntities(String pkg) throws ClassNotFoundException, IOException {
        List<Folder> folders = HeymaPakkageUtils.getFoldersFromPackage(pkg);
        return folders;
    }

    public void executeSQLStatement(String sql) {
        dao.execute(sql);
    }

    public List<Attribute> getAttributesFromEntity(Object entity) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        String struct = PropertyUtils.getProperty(entity, "struct").toString();
        JSONArray jsonArray = JSONArray.fromObject(struct);
        return (List<Attribute>) JSONArray.toCollection(jsonArray, Attribute.class);
    }

    public <T> List<T> getListFromStructProperty(Object entity, Class<T> type, String lstProperty) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        String struct = PropertyUtils.getProperty(entity, lstProperty).toString();
        JSONArray jsonArray = JSONArray.fromObject(struct);
        return (List<T>) JSONArray.toCollection(jsonArray, type);
    }

    public void setListToStructProperty(Object entity, List<? extends Object> objects, String lstProperty) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        Gson gson = new Gson();
        PropertyUtils.setSimpleProperty(entity, lstProperty, gson.toJson(objects));
    }

    public <T> T jsonToObject(String JSON, Class<T> type) {
        Gson gson = new Gson();
        return gson.fromJson(JSON, type);
    }

    @Deprecated
    public List<Attribute> getAttributesOfGroup(List<Attribute> attrs, Grouper group) {
        List<Attribute> gattrs = new ArrayList<Attribute>();
        for (Attribute a : attrs) {
            if (a.getGroup() != null) {
                if (a.getGroup().getId().equals(group.getId())) {
                    gattrs.add(a);
                }
            }
        }
        return gattrs;
    }

    public void updateEntityStruct(Object entity) {
        dao.persist(entity);
    }

    public void alterAddColumn(Entity entity, Attribute attribute) {
        dao.execute(StatementUtil.alterAddColumn(entity, attribute));
        if (attribute.getType().equals(Attribute.ENTITY)) {
            if (attribute.getMultiplicity().equals(Attribute.MULTIPLICITY_1)) {
                dao.execute(StatementUtil.alterAddForeignKey(entity.getId(), attribute.getRel(), attribute.getColumnName()));
            }
        }
    }

    public boolean existTable(String schema, String tablename) {
        return dao.existTable(schema, tablename);
    }

    public boolean existColumn(String schema, String tablename, String columnname) {
        return dao.existColumn(schema, tablename, columnname);
    }

    public void createTable(Entity entity) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        //Entity en = new Entity();
        //en.setSchema("public");
        //en.setName(PropertyUtils.getProperty(entity, "name").toString());
        //en.setAttributes(this.getAttributesFromEntity(entity));
        //dao.execute(StatementUtil.getPGStatement(entity, null));
        dao.execute(StatementUtil.createEntityDataStore(entity));
    }

    public void alterDropColumn(Attribute attr) {
        dao.execute(StatementUtil.alterDropColumn(attr));
    }

    public List<SimpleObject> getSimpleObjectEntityData(Entity entity) {
        List<Object[]> data = dao.selectSQL(StatementUtil.simpleSelectTable(entity, null));
        List<SimpleObject> formdata = new ArrayList<SimpleObject>();

        for (Object[] rec : data) {
            SimpleObject so = new SimpleObject();
            so.setId(new Long(rec[0].toString()));
            so.setValue(rec[entity.getIndexOfAttributeVisible() + 1]);
            formdata.add(so);
        }

        return formdata;
    }

    public void deleteObject(Entity entity, Long id) {
        dao.remove(StatementUtil.deleteObject(entity), id);
    }

    public void alterUpdateColumn(Attribute input) {
    }

    public List<Map<String, Object>> jsonData(Entity entity, Long parent_id) {
        System.out.println("jsonData(): entity parent: " + entity.getParent());
        System.out.println("jsonData(): List of " + entity.getId() + ":" + entity.getName());
        //String SQL = StatementUtil.simpleSelectTable(entity, parent_id);
        //if(entityDataCache.get(entity.getName()) == null) {
            List<Map<String, Object>> lst = dao.find(entity, parent_id);
        //     if(lst.size() <= 100) {
                entityDataCache.put(entity.getName(), lst);
        //    }
            return lst;
        //} else {
        //    System.out.println("jsonData(): Load data from cache ");
        //    return (List<Map<String, Object>>) entityDataCache.get(entity.getName());
        //}        
    }

    public List<Map<String, Object>> jsonData(Entity entity, SimpleFilter filter) {
        String SQL = StatementUtil.simpleSelectTable(entity, null)
                + StatementUtil.createRestrictionFilter(filter);
        System.out.println(SQL);
        return dao.selectSQLMapResult(SQL, filter.getValues());
    }

    public List<Map<String, Object>> getAllObjectsOfEntity(Entity entity) {
        List<Map<String, Object>> result = dao.find(entity, null);
        return result;
    }

    public List<Map<String, Object>> schemaTables(String schema) {
        String SQL = StatementUtil.getInfSchemaTables(schema);
        return dao.selectSQLMapResult(SQL);
    }

    public List<Map<String, Object>> tableColumns(String schema, String tableName) {
        //String SQL = StatementUtil.getInfSchemaColumns(schema, tableName);
        //return dao.selectSQLMapResult(SQL);
        String[] fields = new String[]{"ordinal_position",
            "column_name",
            "is_nullable",
            "data_type",
            "character_maximum_length",
            "udt_name",
            "numeric_precision"};
        SimpleFilter sf = new SimpleFilter();
        sf.addValue("TABLE_SCHEMA", schema);
        sf.addValue("TABLE_NAME", tableName);
        sf.addANDRestriction(new Restriction("TABLE_SCHEMA", Restriction.EQUALS, "TABLE_SCHEMA"));
        sf.addANDRestriction(new Restriction("TABLE_NAME", Restriction.EQUALS, "TABLE_NAME"));
        String SQL = StatementUtil.simpleSelectTable(fields, "information_schema", "columns", sf);
        return dao.find(SQL, sf.getValues());
    }

    public List<Map<String, Object>> tableColumns(List<Map<String, Object>> schemaColumns, String tableName) {
        List<Map<String, Object>> nl = new ArrayList<Map<String, Object>>();
        for (Map<String, Object> t : schemaColumns) {
            if (t.get(Table.TABLE_NAME).equals(tableName)) {
                nl.add(t);
            }
        }
        return nl;
    }

//    public List<Map<String, String>> tableColumnsJson(List<Map<String, Object>> schemaColumns, String tableName) {
//        List<Map<String, String>> nl = new ArrayList<Map<String, String>>();
//        for (Map<String, Object> t : schemaColumns) {
//            if (t.get(Table.TABLE_NAME).equals(tableName)) {
//                Map<String, String> col = new HashMap<String, String>(1);
//                col.put("name", t.get(Table.TABLE_NAME).toString());
//                nl.add(col);
//            }
//        }
//        return nl;
//    }
    public List<Map<String, Object>> schemaColumns(String schema) {
        String SQL = StatementUtil.getInfSchemaColumns(schema);
        return dao.selectSQLMapResult(SQL);
    }

    public Map<String, String> tableColumnsMap(String schema, String tableName) {
        List<Map<String, Object>> columns = this.tableColumns(schema, tableName);
        Map<String, String> mapcolumns = new HashMap<String, String>();
        for (Map<String, Object> c : columns) {
            if (c.get("data_type").equals("USER-DEFINED")) {
                mapcolumns.put(c.get("column_name").toString(), c.get("udt_name").toString());
            } else {
                mapcolumns.put(c.get("column_name").toString(), c.get("data_type").toString());
            }
        }
        return mapcolumns;
    }

    private boolean isInArray(Object value, Object[] array) {
        for (Object o : array) {
            if (o.equals(value)) {
                return true;
            }
        }
        return false;
    }

    public Map<String, String> tableColumnsMap(String schema, String tableName, String[] nofields) {
        List<Map<String, Object>> columns = this.tableColumns(schema, tableName);
        Map<String, String> mapcolumns = new HashMap<String, String>();
        for (Map<String, Object> c : columns) {
            if (!isInArray(c.get("column_name").toString(), nofields)) {
                if (c.get("data_type").equals("USER-DEFINED")) {
                    mapcolumns.put(c.get("column_name").toString(), c.get("udt_name").toString());
                } else {
                    mapcolumns.put(c.get("column_name").toString(), c.get("data_type").toString());
                }
            }
        }
        return mapcolumns;
    }

    public Map<String, String> tableColumnsMap(List<Map<String, Object>> columns) {
        Map<String, String> mapcolumns = new HashMap<String, String>();
        for (Map<String, Object> c : columns) {
            mapcolumns.put(c.get("column_name").toString(), c.get("data_type").toString());
        }
        return mapcolumns;
    }

    public EntityResult get(String entity, Object id) {
        return this.get(Entity.SCHEMA, entity, id);
    }

    public EntityResult get(String entity, Object id, String[] fields) {
        return this.get(Entity.SCHEMA, entity, id, fields);
    }

    public EntityResult get(String schema, String entity, Object id) {
        EntityResult result = new EntityResult();
        Map<String, String> tableMap = this.tableColumnsMap(schema, entity);
        String SQL = StatementUtil.simpleSelectObject(this.tableMapToPostgisColumns(tableMap.entrySet()), schema, entity);
        result.setObjectData(dao.get(SQL, id));
        result.setSuccess(result.getObjectData() == null ? false : true);
        return result;
    }

    public EntityResult get(String schema, String entity, Object id, String[] fields) {
        EntityResult result = new EntityResult();
        String SQL = StatementUtil.simpleSelectObject(fields, schema, entity);
        System.out.println(SQL);
        result.setObjectData(dao.get(SQL, id));
        return result;
    }

    public EntityResult find(String entity) {
          return this.find(Entity.SCHEMA, entity, new String[0]);        
    }
    
    public EntityResult find(String schema, String entity) {
          return this.find(schema, entity, new String[0]);
    }
    
    public EntityResult find(String schema, String entity, String[] orderFields) {
        EntityResult result = new EntityResult();
        String[] nofields = {"the_geom"};
        Map<String, String> tableMap = this.tableColumnsMap(schema, entity, nofields);
        String SQL = StatementUtil.simpleSelectTable(tableMap.keySet(), schema, entity, orderFields);
        System.out.println(SQL);
        result.setListData(dao.selectSQLMapResult(SQL));
        result.setSuccess(Boolean.TRUE);
        return result;
    }

    public EntityResult find(String entity, SimpleFilter filter) {
        return this.find(Entity.SCHEMA, entity, filter);
    }

    public EntityResult find(String schema, String entity, SimpleFilter filter) {
        EntityResult result = new EntityResult();
        String[] nofields = {"the_geom"};
        Map<String, String> tableMap = this.tableColumnsMap(schema, entity, nofields);
        String SQL = StatementUtil.simpleSelectTable(tableMap.keySet(), schema, entity, filter, new String[0]);
        System.out.println(SQL);
        result.setListData(dao.selectSQLMapResult(SQL, filter.getValues()));
        result.setSuccess(Boolean.TRUE);
        return result;
    }        

    public EntityResult find(String entity, SimpleFilter filter, String[] fields) {
        return this.find(Entity.SCHEMA, entity, filter, fields);
    }

    public EntityResult find(String schema, String entity, SimpleFilter filter, String[] fields) {
        EntityResult result = new EntityResult();
        String SQL = StatementUtil.simpleSelectTable(fields, schema, entity, filter);
        System.out.println(SQL);
        result.setListData(dao.selectSQLMapResult(SQL, filter.getValues()));
        result.setSuccess(Boolean.TRUE);
        return result;
    }

    public EntityResult persist(Map<String, Object> insertable, String entity) {
        return this.persist(insertable, Entity.SCHEMA, entity);
    }

    public EntityResult persist(Map<String, Object> insertable, String schema, String entity) {
        return this.persist(insertable, schema, entity, System.currentTimeMillis());
    }

    public EntityResult persist(Map<String, Object> insertable, String schema, String entity, Object id) {
        EntityResult result = new EntityResult();
        Map<String, String> tableMap = this.tableColumnsMap(schema, entity);
        if (insertable.get(entity + "_id") == null) {
            insertable.put(entity + "_id", id);
            Map<String, String> ifields = DynamicEntityMap.getInsertableFields(tableMap, insertable);

            String SQL = StatementUtil.getPersistSQLStatement(tableMap, ifields, schema, entity);
            System.out.println(SQL);

            dao.persist(SQL, ifields, insertable);
            result.setPrimaryKey(id);
        } else {
            Map<String, String> ifields = DynamicEntityMap.getInsertableFields(tableMap, insertable);
            String SQL = StatementUtil.getUpdateSQLStatement(tableMap, insertable, schema, entity, entity + "_id");
            System.out.println(SQL);

            dao.update(SQL, ifields, insertable);
        }
        result.setSuccess(Boolean.TRUE);
        return result;
    }

    @Override
    public void deleteObject(String schema, String entity, Object id) {
        dao.delete(schema, entity, id);
    }        

    public List<Map<String, Object>> getAddOnEntitiess() {
        return dao.selectSQLMapResult("select id, label, name from logic.entidad where visible is not null");
    }

    public boolean existObject(String schema, String entity, String field, Object value) {
        return dao.existObject(schema, entity, field, value);
    }

    public Map<String, Object> getExistObject(String schema, String entity, String field, Object value) {
        SimpleFilter sf = new SimpleFilter();
        sf.addANDRestriction(new Restriction(field, Restriction.EQUALS));
        sf.addValue(field, value);
        return this.find(schema, entity, sf).getObjectData();
    }

    public Map<String, Object> getExistObject(String schema, String entity, String field, Object value, String[] fields) {
        SimpleFilter sf = new SimpleFilter();
        sf.addANDRestriction(new Restriction(field, Restriction.EQUALS));
        sf.addValue(field, value);
        return this.find(schema, entity, sf, fields).getObjectData();
    }

    public Map<String, Object> buildPersistenObjectFromRequest(String schema, String entity, HttpServletRequest request) throws ParseException {
        Map<String, Object> input = DynamicEntityMap.requestMapToEntityMap(request.getParameterMap());
        Map<String, String> tableMap = tableColumnsMap(schema, entity);
        Map<String, Object> insertable = DynamicEntityMap.parseInputMap(input, tableMap);
        return insertable;
    }

    public Set<String> tableMapToPostgisColumns(Set<Entry<String, String>> entries) {
        Set<String> cols = new HashSet<String>();
        for (Entry e : entries) {
            System.out.println("---- " + e.getKey() + " - " + e.getValue());
            if (e.getValue().equals(Attribute.GEOMETRY)) {
                cols.add("ST_AsText(" + e.getKey() + ") AS " + e.getKey()
                        + ", ST_AsGeoJSON(" + e.getKey() + ") AS _geo_json");
            } else {
                cols.add((String) e.getKey());
            }
        }
        return cols;
    }    

    @Override
    public Entity getFinalEntityByID(String id) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    @Override
    public List arrayEntityData(String id) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    @Override
    public List jsonEntityData(String id) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    @Override
    public List<Element> EntitiesToExtTreeElements() {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    @Override
    public List<Map<String, Object>> noEntityDataBase() {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }
}
